package com.jourwon.spring.boot.exception;

import com.jourwon.spring.boot.enums.CommonResponseCodeEnum;
import com.jourwon.spring.boot.model.vo.CommonResponseVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.RestClientException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.util.NestedServletException;
import org.yaml.snakeyaml.constructor.DuplicateKeyException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.Set;

/**
 * 全局的异常处理
 *
 * @author JourWon
 * @date 2020/12/3
 */
@Slf4j
@RestControllerAdvice
public class CommonExceptionHandler {

    /**
     * 顶级的异常处理
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({Exception.class})
    public CommonResponseVO<?> handleException(Exception e) {
        log.error("[Exception] 未知异常", e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.SYSTEM_EXCEPTION);
    }

    /**
     * 自定义的异常处理
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({CommonException.class})
    public CommonResponseVO<?> handleCommonException(CommonException e) {
        log.error("[CommonException] 自定义异常", e);
        return new CommonResponseVO<>(e.getCode(), e.getMessage());
    }

    /**
     * 嵌套servlet异常
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({NestedServletException.class})
    public CommonResponseVO<?> handleNestedServletException(NestedServletException e) {
        log.error("[NestedServletException] 嵌套servlet异常", e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.SYSTEM_EXCEPTION);
    }

    /**
     * restClient异常处理
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({RestClientException.class})
    public CommonResponseVO<?> handleRestClientException(RestClientException e) {
        log.error("[RestClientException] RestTemplate请求异常", e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.SYSTEM_EXCEPTION);
    }

    /**
     * 数据库字段重复
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({DuplicateKeyException.class})
    public CommonResponseVO<?> handleDuplicateKeyException(DuplicateKeyException e) {
        log.error("[DuplicateKeyException] 数据库字段重复", e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.DATABASE_FIELD_DUPLICATE);
    }

    /**
     * javax.validation 校验参数时抛出的异常
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({ValidationException.class})
    public CommonResponseVO<?> handleValidationException(ValidationException e) {
        log.error("[ValidationException] 参数校验失败", e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.REQUEST_PARAMETER_ILLEGAL, e.getMessage());
    }

    // == 参数校验 begin ==

    /**
     * 缺少servlet请求参数时抛出的异常
     * 表单请求，缺少请求参数，如：@RequestParam(value = "age", required = true)使用了required = true
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({MissingServletRequestParameterException.class})
    public CommonResponseVO<?> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        log.error("[MissingServletRequestParameterException] 缺少参数", e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.REQUEST_PARAMETER_ILLEGAL, "缺少参数:" + e.getParameterName());
    }

    /**
     * 方法参数类型不匹配异常
     * 表单请求，参数类型不匹配时抛出的异常
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({MethodArgumentTypeMismatchException.class})
    public CommonResponseVO<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        log.error("[MethodArgumentTypeMismatchException] 方法参数类型不匹配异常", e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.REQUEST_PARAMETER_ILLEGAL);
    }

    /**
     * 参数校验异常
     * 表单请求，请求参数(非 java bean)校验失败时抛出的异常
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({ConstraintViolationException.class})
    public CommonResponseVO<?> handleConstraintViolationException(ConstraintViolationException e) {
        // List<Map<String, String>> list = new ArrayList<>();
        // // e.getMessage() 的格式为 getUser.id: id不能为空, getUser.name: name不能为空
        // String[] messages = e.getMessage().split(", ");
        // for (String msg : messages) {
        //     String[] fieldAndMsg = msg.split(": ");
        //     String field = fieldAndMsg[0].split("\\.")[1];
        //     String message = fieldAndMsg[1];
        //
        //     Map<String, String> map = new HashMap<>(8);
        //     map.put("field", field);
        //     map.put("message", message);
        //     list.add(map);
        // }
        // return new CommonResponseVO<>(CommonResponseCodeEnum.REQUEST_PARAMETER_ILLEGAL, list);

        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        ConstraintViolation<?> violation = violations.iterator().next();
        String message = violation.getMessage();
        log.error("[ConstraintViolationException] 参数校验失败:{}", message, e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.REQUEST_PARAMETER_ILLEGAL, message);
    }

    /**
     * 参数绑定异常
     * 表单请求，绑定到 java bean 出错时会抛出 BindException 异常
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({BindException.class})
    public CommonResponseVO<?> handleBindException(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        String message = getBindResultMessage(bindingResult);
        log.error("[BindException] 参数绑定失败:{}", message, e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.REQUEST_PARAMETER_ILLEGAL, message);
    }

    /**
     * 请求参数解析失败异常
     * json请求，请求参数解析失败时抛出的异常，比如传入和接收的参数类型不一致
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({HttpMessageNotReadableException.class})
    public CommonResponseVO<?> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.error("[HttpMessageNotReadableException] 请求参数解析失败", e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.PARAMETER_TYPE_ILLEGAL);
    }

    /**
     * 将请求体解析并绑定到 java bean 时，如果出错，则抛出 MethodArgumentNotValidException 异常
     * json请求，请求参数校验失败时抛出的异常
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public CommonResponseVO<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        String message = getBindResultMessage(bindingResult);
        log.error("[MethodArgumentNotValidException] 请求参数校验失败:{}", message, e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.REQUEST_PARAMETER_ILLEGAL, message);
    }

    /**
     * 获取绑定结果信息
     *
     * @param bindingResult 绑定结果
     * @return String 绑定结果信息
     */
    private String getBindResultMessage(BindingResult bindingResult) {
        FieldError error = bindingResult.getFieldError();
        String field = error != null ? error.getField() : "空";
        String message = error != null ? error.getDefaultMessage() : "空";
        return String.format("参数%s绑定失败,失败原因:%s", field, message);
    }

    // == 参数校验 end ==

    /**
     * 不支持当前请求方法时抛出的异常
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public CommonResponseVO<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        log.error("[HttpRequestMethodNotSupportedException] 不支持当前请求方法", e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.NONSUPPORT_REQUEST_TYPE);
    }

    /**
     * 不支持当前媒体类型时抛出的异常
     *
     * @param e 异常
     * @return CommonResponseVO 统一响应对象
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler({HttpMediaTypeNotSupportedException.class})
    public CommonResponseVO<?> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
        log.error("[HttpMediaTypeNotSupportedException] 不支持当前媒体类型", e);
        return new CommonResponseVO<>(CommonResponseCodeEnum.REQUEST_PARAMETER_ILLEGAL);
    }

}

